#ifndef __tenacitas_log__logger__h__
#define __tenacitas_log__logger__h__


// =============================================================================
/** @file */

// =============================================================================
/** todo
    @todo implement class @p binary_file_printer and its consequences
*/


// =============================================================================
//                                 C++ Headers
#include <string>
#include <cstdint>
#include <mutex>
#include <map>
#include <utility>
#include <sstream>
#include <memory>
#include <functional>

// =============================================================================
//                                 3rds Headers
#include <boost/thread/thread.hpp>

// =============================================================================
//                                 Our Headers
#include "level.h"

// =============================================================================
//                                 Namespaces
using namespace std;


// =============================================================================
//                                 Macro Contants
/** */


// =============================================================================
//                                 Macro Commands
/** */
#define log_trace( logger ) logger.new_line( tenacitas::log::level::trace, __FILE__, __func__, __LINE__ )

#define log_debug( logger ) logger.new_line( tenacitas::log::level::debug, __FILE__, __func__, __LINE__ )

#define log_info( logger ) logger.new_line( tenacitas::log::level::info, __FILE__, __func__, __LINE__ )

#define log_warn( logger ) logger.new_line( tenacitas::log::level::warn, __FILE__, __func__, __LINE__ )

#define log_error( logger ) logger.new_line( tenacitas::log::level::error, __FILE__, __func__, __LINE__ )

#define log_critical( logger ) logger.new_line( tenacitas::log::level::critical, __FILE__, __func__, __LINE__ )


/** namespace of the organization */
namespace tenacitas {
    namespace log {

        // =========================== Typedefs ===========================

        // ============================ Pre-Declarations ==================

        // ============================ Attributes ========================
        /** */

        // ============================ Functions =========================
        
        // ============================ Classes ===========================
        /** 

            @nosubgrouping 
        */
        template <typename t_printer>
        class logger_t {

        public:

            //    logger - Public Internal Types 

            /** */
            typedef t_printer printer;

            /** */
            typedef typename printer::ptr printer_ptr;

            /** */
            typedef logger_t<printer> logger;

            /** */
            typedef std::shared_ptr<logger> logger_ptr;
            

            //    logger - Public Constructors

            /** */
            ~logger_t( ) {
            }

            /** */
            static void config( const level & p_level_all ) {
                m_level_all = p_level_all;
            }


            /** */
            logger_t( level p_level = logger::get_level_all( ) )
                : m_thread_id( boost::this_thread::get_id( ) ),
                  m_printer( get_printer( m_thread_id ) ),
                  m_level( p_level ) { }            
            

            /** */
            logger_t ( const logger_t & p_logger ) = delete;            

            /** */
            logger_t ( logger_t && p_logger ) = delete;

            //    logger - Public Accessors

            /** Sets log level for all the @p logger objects

                It only affects logger objects created after this method is
                called */
            static void set_level_all( const level & p_level_all ) {
                m_level_all = p_level_all;
            }

            /** Gets the log level for all the @p logger objects */
            static level get_level_all( ) {
                return m_level_all;
            }
            

            /** */
            void set_level( const level & p_level ) {
                m_level = p_level;
            }

            /** */
            const level & get_level( ) const {
                return m_level;
            }

            /** */
            bool can_log( const level & p_level ) const {
                if ( ( p_level == level::error ) ||
                     ( p_level == level::critical ) ) {
                    return true;
                }
                return ( p_level >= m_level );
            }
	    
            //    logger - Public Processors

            /** */
            logger_t & new_line( level p_level, const char * p_file,
                                 const char * p_function, int64_t p_line ) {
                if( m_thread_id != boost::this_thread::get_id( ) ) {

                    std::stringstream l_stream;

                    int64_t l_this_thread_id = th2int( m_thread_id );

                    int64_t l_new_thread_id =
                        th2int( boost::this_thread::get_id( ) );
                    
                    l_stream << "This log is to be used with thread " << std::dec <<
                        l_this_thread_id << ", but its being used with thread " <<
                        std::dec << l_new_thread_id ;
                    throw ( l_stream.str( ) );
                }
                
                if ( can_log( p_level ) ) {
                    m_printer->new_line( p_level, p_file, p_function, p_line );
                }
                return *this;
            }

            //    logger - Public Operators

            /** */
            void operator &() = delete;

            /** */
            void * operator new( size_t ) = delete;
            
            
            /** */
            logger_t & operator = ( const logger_t & p_logger ) = delete;                

            /** */
            logger_t & operator = ( logger_t && p_logger ) = delete;                

            /** */
            bool operator == ( const logger_t & p_logger ) const {
                return ( m_thread_id == p_logger.m_thread_id );
            }

            /** */
            bool operator != ( const logger_t & p_logger ) const {
                return ( m_thread_id != p_logger.m_thread_id );
            }

            /** */
            bool operator < ( const logger_t & p_logger ) const {
                return ( m_thread_id < p_logger.m_thread_id );
            }

            /** */
            bool operator > ( const logger_t & p_logger ) const {
                return ( m_thread_id > p_logger.m_thread_id );
            }

            /** */
            bool operator <= ( const logger_t & p_logger ) const {
                return ( m_thread_id <= p_logger.m_thread_id );
            }

            /** */
            bool operator >= ( const logger_t & p_logger ) const {
                return ( m_thread_id >= p_logger.m_thread_id );
            }

            /** */
            template <typename t_data>
            logger_t & operator << ( const t_data & p_data ) {
                m_printer->write( p_data );
                return *this;
            }

        private:

            //    logger - Private Internal Types

            /** Function object to be called when a thread ends */
            struct at_thread_end {
                at_thread_end( ) {}

                at_thread_end( const at_thread_end & p_at_thread_end ) {}
                
                at_thread_end( at_thread_end && p_at_thread_end ) {}

                at_thread_end & operator=( at_thread_end && p_at_thread_end ) {
                    return *this;
                }

                at_thread_end & operator=( const at_thread_end & p_r ) {
                    return *this;
                }

                void operator( )( ) {
                    logger_t::delete_logger( boost::this_thread::get_id( ) );
                }

                void operator( )( boost::thread::id p_thread_id ) {
                    logger_t::delete_logger( p_thread_id );
                }
            };

            /** */
            typedef std::map<boost::thread::id, printer_ptr> threads_loggers;

            /** */
            typedef typename threads_loggers::iterator loggers_iterator;

            //    logger - Private Processors
            
            /** */
            static printer_ptr get_printer( boost::thread::id p_thread_id ) {

                // already created?
                loggers_iterator l_ite = m_threads_loggers.find( p_thread_id ); 

                if ( l_ite != m_threads_loggers.end( ) ) {
                    // yes!
                    return ( l_ite->second );
                }

                // inserting the logger into the collection of loggers,
                // associating to theirs threads

                printer_ptr l_printer = printer::create( p_thread_id );

                std::pair<boost::thread::id, printer_ptr> l_pair( p_thread_id, l_printer ); 

                std::lock_guard<std::mutex> lock(m_mutex);

                // I can lock only here, because the thread l_th_id can not be
                // modified in another thread
                boost::this_thread::at_thread_exit( at_thread_end( ) );

                // returning a reference to the just inserted logger
                return (  m_threads_loggers.insert( l_pair ).first->second );   
            }

            /** */
            static void delete_logger( boost::thread::id p_thread_id ) {

                loggers_iterator l_ite = m_threads_loggers.find( p_thread_id );

                if ( l_ite != m_threads_loggers.end( ) ) {

                    {
                        // I can lock only here, because exclusion of p_thread_id can
                        // not happen in another thread
                        std::lock_guard<std::mutex> lock( m_mutex );

                        m_threads_loggers.erase( l_ite );
                    }
                }
            }


            //    logger - Private Attributes

            /** */
            static threads_loggers m_threads_loggers;

            /** */
            static std::mutex m_mutex;

            /** */
            static level m_level_all;
            

            /** */
            boost::thread::id m_thread_id;

            /** */
            printer_ptr m_printer;

            /** */
            level m_level;
        };
    }
}

using namespace tenacitas::log;

/** */
template <typename t_printer>
typename logger_t<t_printer>::threads_loggers
logger_t<t_printer>::
m_threads_loggers;

/** */
template <typename t_printer>
std::mutex
logger_t<t_printer>::
m_mutex;

/** */
template <typename t_printer>
level
logger_t<t_printer>::
m_level_all( level::info );


#endif

